// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>
#include <stdint.h>
#include <utility>

#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/public/test/async_file_test_helper.h"
#include "content/public/test/mock_special_storage_policy.h"
#include "content/public/test/test_file_system_backend.h"
#include "content/public/test/test_file_system_context.h"
#include "storage/browser/blob/shareable_file_reference.h"
#include "storage/browser/fileapi/copy_or_move_file_validator.h"
#include "storage/browser/fileapi/external_mount_points.h"
#include "storage/browser/fileapi/file_system_backend.h"
#include "storage/browser/fileapi/file_system_context.h"
#include "storage/browser/fileapi/file_system_url.h"
#include "storage/browser/fileapi/isolated_context.h"
#include "storage/common/fileapi/file_system_util.h"
#include "testing/gtest/include/gtest/gtest.h"

using content::AsyncFileTestHelper;
using storage::CopyOrMoveFileValidator;
using storage::CopyOrMoveFileValidatorFactory;
using storage::FileSystemURL;

namespace content {

namespace {

    const storage::FileSystemType kNoValidatorType = storage::kFileSystemTypeTemporary;
    const storage::FileSystemType kWithValidatorType = storage::kFileSystemTypeTest;

    void ExpectOk(const GURL& origin_url,
        const std::string& name,
        base::File::Error error)
    {
        ASSERT_EQ(base::File::FILE_OK, error);
    }

    class CopyOrMoveFileValidatorTestHelper {
    public:
        CopyOrMoveFileValidatorTestHelper(const GURL& origin,
            storage::FileSystemType src_type,
            storage::FileSystemType dest_type)
            : origin_(origin)
            , src_type_(src_type)
            , dest_type_(dest_type)
        {
        }

        ~CopyOrMoveFileValidatorTestHelper()
        {
            file_system_context_ = NULL;
            base::RunLoop().RunUntilIdle();
        }

        void SetUp()
        {
            ASSERT_TRUE(base_.CreateUniqueTempDir());
            base::FilePath base_dir = base_.GetPath();

            file_system_context_ = CreateFileSystemContextForTesting(NULL, base_dir);

            // Set up TestFileSystemBackend to require CopyOrMoveFileValidator.
            storage::FileSystemBackend* test_file_system_backend = file_system_context_->GetFileSystemBackend(kWithValidatorType);
            static_cast<TestFileSystemBackend*>(test_file_system_backend)->set_require_copy_or_move_validator(true);

            // Sets up source.
            storage::FileSystemBackend* src_file_system_backend = file_system_context_->GetFileSystemBackend(src_type_);
            src_file_system_backend->ResolveURL(
                FileSystemURL::CreateForTest(origin_, src_type_, base::FilePath()),
                storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
                base::Bind(&ExpectOk));
            base::RunLoop().RunUntilIdle();
            ASSERT_EQ(base::File::FILE_OK, CreateDirectory(SourceURL("")));

            // Sets up dest.
            DCHECK_EQ(kWithValidatorType, dest_type_);
            ASSERT_EQ(base::File::FILE_OK, CreateDirectory(DestURL("")));

            copy_src_ = SourceURL("copy_src.jpg");
            move_src_ = SourceURL("move_src.jpg");
            copy_dest_ = DestURL("copy_dest.jpg");
            move_dest_ = DestURL("move_dest.jpg");

            ASSERT_EQ(base::File::FILE_OK, CreateFile(copy_src_, 10));
            ASSERT_EQ(base::File::FILE_OK, CreateFile(move_src_, 10));

            ASSERT_TRUE(FileExists(copy_src_, 10));
            ASSERT_TRUE(FileExists(move_src_, 10));
            ASSERT_FALSE(FileExists(copy_dest_, 10));
            ASSERT_FALSE(FileExists(move_dest_, 10));
        }

        void SetMediaCopyOrMoveFileValidatorFactory(
            std::unique_ptr<storage::CopyOrMoveFileValidatorFactory> factory)
        {
            TestFileSystemBackend* backend = static_cast<TestFileSystemBackend*>(
                file_system_context_->GetFileSystemBackend(kWithValidatorType));
            backend->InitializeCopyOrMoveFileValidatorFactory(std::move(factory));
        }

        void CopyTest(base::File::Error expected)
        {
            ASSERT_TRUE(FileExists(copy_src_, 10));
            ASSERT_FALSE(FileExists(copy_dest_, 10));

            EXPECT_EQ(expected,
                AsyncFileTestHelper::Copy(
                    file_system_context_.get(), copy_src_, copy_dest_));

            EXPECT_TRUE(FileExists(copy_src_, 10));
            if (expected == base::File::FILE_OK)
                EXPECT_TRUE(FileExists(copy_dest_, 10));
            else
                EXPECT_FALSE(FileExists(copy_dest_, 10));
        };

        void MoveTest(base::File::Error expected)
        {
            ASSERT_TRUE(FileExists(move_src_, 10));
            ASSERT_FALSE(FileExists(move_dest_, 10));

            EXPECT_EQ(expected,
                AsyncFileTestHelper::Move(
                    file_system_context_.get(), move_src_, move_dest_));

            if (expected == base::File::FILE_OK) {
                EXPECT_FALSE(FileExists(move_src_, 10));
                EXPECT_TRUE(FileExists(move_dest_, 10));
            } else {
                EXPECT_TRUE(FileExists(move_src_, 10));
                EXPECT_FALSE(FileExists(move_dest_, 10));
            }
        };

    private:
        FileSystemURL SourceURL(const std::string& path)
        {
            return file_system_context_->CreateCrackedFileSystemURL(
                origin_, src_type_,
                base::FilePath().AppendASCII("src").AppendASCII(path));
        }

        FileSystemURL DestURL(const std::string& path)
        {
            return file_system_context_->CreateCrackedFileSystemURL(
                origin_, dest_type_,
                base::FilePath().AppendASCII("dest").AppendASCII(path));
        }

        base::File::Error CreateFile(const FileSystemURL& url, size_t size)
        {
            base::File::Error result = AsyncFileTestHelper::CreateFile(file_system_context_.get(), url);
            if (result != base::File::FILE_OK)
                return result;
            return AsyncFileTestHelper::TruncateFile(
                file_system_context_.get(), url, size);
        }

        base::File::Error CreateDirectory(const FileSystemURL& url)
        {
            return AsyncFileTestHelper::CreateDirectory(file_system_context_.get(),
                url);
        }

        bool FileExists(const FileSystemURL& url, int64_t expected_size)
        {
            return AsyncFileTestHelper::FileExists(
                file_system_context_.get(), url, expected_size);
        }

        base::ScopedTempDir base_;

        const GURL origin_;

        const storage::FileSystemType src_type_;
        const storage::FileSystemType dest_type_;
        std::string src_fsid_;
        std::string dest_fsid_;

        base::MessageLoop message_loop_;
        scoped_refptr<storage::FileSystemContext> file_system_context_;

        FileSystemURL copy_src_;
        FileSystemURL copy_dest_;
        FileSystemURL move_src_;
        FileSystemURL move_dest_;

        DISALLOW_COPY_AND_ASSIGN(CopyOrMoveFileValidatorTestHelper);
    };

    // For TestCopyOrMoveFileValidatorFactory
    enum Validity {
        VALID,
        PRE_WRITE_INVALID,
        POST_WRITE_INVALID
    };

    class TestCopyOrMoveFileValidatorFactory
        : public storage::CopyOrMoveFileValidatorFactory {
    public:
        // A factory that creates validators that accept everything or nothing.
        // TODO(gbillock): switch args to enum or something
        explicit TestCopyOrMoveFileValidatorFactory(Validity validity)
            : validity_(validity)
        {
        }
        ~TestCopyOrMoveFileValidatorFactory() override { }

        storage::CopyOrMoveFileValidator* CreateCopyOrMoveFileValidator(
            const FileSystemURL& /*src_url*/,
            const base::FilePath& /*platform_path*/) override
        {
            return new TestCopyOrMoveFileValidator(validity_);
        }

    private:
        class TestCopyOrMoveFileValidator : public CopyOrMoveFileValidator {
        public:
            explicit TestCopyOrMoveFileValidator(Validity validity)
                : result_(validity == VALID || validity == POST_WRITE_INVALID ? base::File::FILE_OK : base::File::FILE_ERROR_SECURITY)
                , write_result_(validity == VALID || validity == PRE_WRITE_INVALID ? base::File::FILE_OK : base::File::FILE_ERROR_SECURITY)
            {
            }
            ~TestCopyOrMoveFileValidator() override { }

            void StartPreWriteValidation(
                const ResultCallback& result_callback) override
            {
                // Post the result since a real validator must do work asynchronously.
                base::ThreadTaskRunnerHandle::Get()->PostTask(
                    FROM_HERE, base::Bind(result_callback, result_));
            }

            void StartPostWriteValidation(
                const base::FilePath& dest_platform_path,
                const ResultCallback& result_callback) override
            {
                // Post the result since a real validator must do work asynchronously.
                base::ThreadTaskRunnerHandle::Get()->PostTask(
                    FROM_HERE, base::Bind(result_callback, write_result_));
            }

        private:
            base::File::Error result_;
            base::File::Error write_result_;

            DISALLOW_COPY_AND_ASSIGN(TestCopyOrMoveFileValidator);
        };

        Validity validity_;

        DISALLOW_COPY_AND_ASSIGN(TestCopyOrMoveFileValidatorFactory);
    };

} // namespace

TEST(CopyOrMoveFileValidatorTest, NoValidatorWithinSameFSType)
{
    // Within a file system type, validation is not expected, so it should
    // work for kWithValidatorType without a validator set.
    CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"),
        kWithValidatorType,
        kWithValidatorType);
    helper.SetUp();
    helper.CopyTest(base::File::FILE_OK);
    helper.MoveTest(base::File::FILE_OK);
}

TEST(CopyOrMoveFileValidatorTest, MissingValidator)
{
    // Copying or moving into a kWithValidatorType requires a file
    // validator.  An error is expected if copy is attempted without a validator.
    CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"),
        kNoValidatorType,
        kWithValidatorType);
    helper.SetUp();
    helper.CopyTest(base::File::FILE_ERROR_SECURITY);
    helper.MoveTest(base::File::FILE_ERROR_SECURITY);
}

TEST(CopyOrMoveFileValidatorTest, AcceptAll)
{
    CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"),
        kNoValidatorType,
        kWithValidatorType);
    helper.SetUp();
    std::unique_ptr<CopyOrMoveFileValidatorFactory> factory(
        new TestCopyOrMoveFileValidatorFactory(VALID));
    helper.SetMediaCopyOrMoveFileValidatorFactory(std::move(factory));

    helper.CopyTest(base::File::FILE_OK);
    helper.MoveTest(base::File::FILE_OK);
}

TEST(CopyOrMoveFileValidatorTest, AcceptNone)
{
    CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"),
        kNoValidatorType,
        kWithValidatorType);
    helper.SetUp();
    std::unique_ptr<CopyOrMoveFileValidatorFactory> factory(
        new TestCopyOrMoveFileValidatorFactory(PRE_WRITE_INVALID));
    helper.SetMediaCopyOrMoveFileValidatorFactory(std::move(factory));

    helper.CopyTest(base::File::FILE_ERROR_SECURITY);
    helper.MoveTest(base::File::FILE_ERROR_SECURITY);
}

TEST(CopyOrMoveFileValidatorTest, OverrideValidator)
{
    // Once set, you can not override the validator.
    CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"),
        kNoValidatorType,
        kWithValidatorType);
    helper.SetUp();
    std::unique_ptr<CopyOrMoveFileValidatorFactory> reject_factory(
        new TestCopyOrMoveFileValidatorFactory(PRE_WRITE_INVALID));
    helper.SetMediaCopyOrMoveFileValidatorFactory(std::move(reject_factory));

    std::unique_ptr<CopyOrMoveFileValidatorFactory> accept_factory(
        new TestCopyOrMoveFileValidatorFactory(VALID));
    helper.SetMediaCopyOrMoveFileValidatorFactory(std::move(accept_factory));

    helper.CopyTest(base::File::FILE_ERROR_SECURITY);
    helper.MoveTest(base::File::FILE_ERROR_SECURITY);
}

TEST(CopyOrMoveFileValidatorTest, RejectPostWrite)
{
    CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"),
        kNoValidatorType,
        kWithValidatorType);
    helper.SetUp();
    std::unique_ptr<CopyOrMoveFileValidatorFactory> factory(
        new TestCopyOrMoveFileValidatorFactory(POST_WRITE_INVALID));
    helper.SetMediaCopyOrMoveFileValidatorFactory(std::move(factory));

    helper.CopyTest(base::File::FILE_ERROR_SECURITY);
    helper.MoveTest(base::File::FILE_ERROR_SECURITY);
}

} // namespace content
